package program;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import kernel.IOManager;
import kernel.ProcessManager;
import kernel.exception.OSException;
import kernel.process.AbstractProcess;
import kernel.process.AbstractProcessHeader;
import shell.OutputMessage;
import shell.ShellWindow;

/**
 * Shell process.
 * For more see man() method.
 * @author Jaromir Lepic, Veronika Vencurikova
 *
 */
public class Shell extends AbstractProcess implements Runnable {
	public static final String WINDOWED = "-w";
	public static final String WINDOWED2 = "--window";
	public static final String WINDOWED3 = "--windowed";
	public static final String SILENT = "-s";
	public static final String SILENT2 = "--silent";
	private Thread thread;
	private long ID;
	private String greeting = "New shell instance launched!";
	private String greetingWindowed = "New windowed shell instance launched";
	
	private boolean windowedRun = false, silentRun = false;
	private ShellWindow shellWindow;
	private Queue<AbstractProcessHeader[]> messageQueue;
	private Queue<OutputMessage> messageToWindow = null;
	private File workingDir;
	
	public Shell(AbstractProcessHeader header, long pid) {
		super(header, pid);
		thread = new Thread(this);
		workingDir = IOManager.getInstance().getWorkingDirectory(shellID);
		messageQueue = new LinkedBlockingQueue<AbstractProcessHeader[]>();
		messageToWindow = new LinkedBlockingQueue<OutputMessage>();
	}
	
	/**
	 * Gets the execution thread of this shell
	 * @return Thread that this process starts
	 */
	public Thread getThread(){
		return thread;
	}

	/**
	 * Gets current ID
	 * @return ID of this shell
	 */
	public long getID(){
		return ID;
	}
	
	/**
	 * Sets new ID
	 * @param ID new ID
	 * @return instance of this shell
	 */
	public Shell setID(long ID){
		this.ID = ID;
		return this;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String doJob(String input) throws OSException {
		for (String param: params){
			if (param.toLowerCase().equals(WINDOWED) ||
					param.toLowerCase().equals(WINDOWED2) ||
					param.toLowerCase().equals(WINDOWED3)){
				windowedRun = true;

			} else if (param.toLowerCase().equals(SILENT) ||
					param.toLowerCase().equals(SILENT2)){
				silentRun = true;
			}
		}
		thread.setDaemon(true);
		if (windowedRun){
			Thread r = IOManager.getInstance().createShellWindow(this, IOManager.getInstance().getNextShellID(), shellID);
			try {
				r.join();
			} catch (InterruptedException e) {
			}
			if (silentRun)
				return "";
			else 
				return greetingWindowed;
		} else {
			IOManager.getInstance().registerShell(this, shellID);
			if (silentRun)
				return "";
			else
				return greeting;
		}
	}
	
	/**
	 * Sets new shellwindow for this shell
	 * @param shellWindow New shell window
	 * @return Instane of this shell
	 */
	public Shell setShellWindow (ShellWindow shellWindow){
		this.shellWindow = shellWindow;
		shellWindow.setIOClient(this);
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void kill() throws OSException {
		IOManager.getInstance().unregisterShell(this, ID);
		synchronized (thread) {
			thread.notify();
		}
	}
	
	/**
	 * Gets the connected shell window
	 * @return Connected shell window
	 */
	public ShellWindow getShellWindow() {
		return shellWindow;
	}
	
	/**
	 * Sets a new message to be printed in connected window
	 * @param message Message to be printed
	 * @param finalMessage If working directory will be printed after message
	 * @param senderToNotify Tread to be notfied of print
	 * @return Instance of this shell
	 */
	public Shell setMessageToWindow(String message, boolean finalMessage, final Thread senderToNotify){
		if (senderToNotify != null){
			this.messageToWindow.add(new OutputMessage(message, finalMessage, senderToNotify));
		} else {
			this.messageToWindow.add(new OutputMessage(message, finalMessage));
		}
		return this;
	}
	
	/**
	 * Sets new working directory for this shell
	 * @param newWorkingDir New working directory
	 * @return Instance of this shell
	 */
	public Shell setNewWorkingDirectory(File newWorkingDir){
		this.workingDir = newWorkingDir;
		return this;
	}
	
	/**
	 * Gets the working directory of this shell
	 * @return Working directory
	 */
	public File getWorkingDirectory(){
		return workingDir;
	}

	/**
	 * Returns message queue in this shell
	 * @return Queue of AbstractProcessHeaders
	 */
	public Queue<AbstractProcessHeader[]> getMessageQueue() {
		return messageQueue;
	}
	
	/**
	 * Tells the kernerl to start processes from given header
	 * @param headers Headers to be passed to start processes
	 */
	private void startProcesses(AbstractProcessHeader[] headers){
				List<AbstractProcessHeader> list = new ArrayList<AbstractProcessHeader>();
				for (AbstractProcessHeader header: headers){
					list.add(header);
				}
				ProcessManager.getInstance().checkAndCreateProcess(list);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void run() {
		this.userID = "root";
		this.shellID = ID;
		if (windowedRun){
			List<AbstractProcessHeader> list = new ArrayList<AbstractProcessHeader>();
			list.add(new AbstractProcessHeader("login", new ArrayList<String>(), "", "", false, ID, "root"));
			ProcessManager.getInstance().checkAndCreateProcess(list);
		}
		while (!shouldEnd){
			while (!messageToWindow.isEmpty()){
				shellWindow.setOutput(messageToWindow.poll(), workingDir);
			}
			if(messageQueue.isEmpty()){
				try {
					synchronized (this.thread) {
						this.thread.wait();
					}
				} catch (InterruptedException e) {
				}
			} else {
				while (!messageQueue.isEmpty()){
					AbstractProcessHeader[] headers = messageQueue.poll();
					startProcesses(headers);
				}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean needParams() {
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String continueJob() throws OSException {
		dispatcher.interrupt();
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected String finalizeJob() throws OSException {
		if (silentRun && windowedRun){
				return null;
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String man(String command) {
	String man = "";
		
		man += "    launches new shell instance\n";
		man += "\n";
		man += "Command syntax:\n";
		man += "    shell [OPTION]\n";
		man += "\n";
		man += "Description:\n";
		man += "    -s - prints no message\n";
		man += "    -w - creates new shell in new window\n";

		return man;
	}

}
